package main

import "bytes"
import "io/ioutil"
import "strconv"

import (
    "fmt"
    "time"
    "net/http"
	"encoding/json"
    "appengine"
    "appengine/datastore"
	"appengine/urlfetch"
)

func init() {
	http.HandleFunc("/register_gcm",       register_gcm)
	http.HandleFunc("/list_registered",    list_registered)
	http.HandleFunc("/list_notifications", list_notifications)
	http.HandleFunc("/send_notifications", send_notifications)
	http.HandleFunc("/delivery_notify",    delivery_notify)
	http.HandleFunc("/export_delivery",    export_delivery)
	http.HandleFunc("/notify_online",      notify_online)
    http.HandleFunc("/", handler)
}

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello, world!")
}

type Device struct {
	GcmId  string
	UserId string
}

func register_gcm(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	c.Debugf("register_gcm request")

	var m map[string]interface{}
	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&m); err != nil {
		c.Errorf("Failed to parse input JSON object %s", err.Error())
		http.Error(w, "Failed to parse input JSON object", http.StatusInternalServerError)
		return
	}

	dev := Device{
		GcmId  : m["gcm_id"].(string),
		UserId : m["user_id"].(string),
	}

	q := datastore.NewQuery("device").Filter("GcmId =", dev.GcmId)
	var devs []Device
	_, err := q.GetAll(c, &devs)

	if err != nil {
		c.Errorf("Failed to fetch device")
		return
	}

	if len(devs) > 0 {
		c.Infof("Device already registered")
		return
	}

	_, err = datastore.Put(c, datastore.NewIncompleteKey(c, "device", nil), &dev)
	if err != nil {
		c.Errorf("register_gcm: database put failed: %s", err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func list_registered(w http.ResponseWriter, r *http.Request) {
	var devs []Device

	c := appengine.NewContext(r)
	keys, err := datastore.NewQuery("device").GetAll(c, &devs)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	for i, dev := range devs {
        k := keys[i]
		fmt.Fprint(w, k, " ", dev, "\n")
	}
}

type GcmMessage struct {
    RegistrationIDs []string          `json:"registration_ids"`
    CollapseKey     string            `json:"collapse_key,omitempty"`
    DelayWhileIdle  bool              `json:"delay_while_idle,omitempty"`
    Data            map[string]string `json:"data,omitempty"`
    TimeToLive      int               `json:"time_to_live,omitempty"`
}

type Notification struct {
	TimeSent       time.Time
}

func send_notifications(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	c.Debugf("send_notification: started")

	var devs []Device
	_, err := datastore.NewQuery("device").GetAll(c, &devs)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	c.Debugf("send_notifications: %d devices found", len(devs))

	if len(devs) == 0 {
		c.Infof("send_notifications: No devices in DB")
		return
	}

	messageid := datastore.NewIncompleteKey(c, "notification", nil)

	message := GcmMessage{
		Data : make(map[string]string),
	}
	message.Data["message_id"]  = messageid.Encode()
	message.Data["server_time"] = strconv.Itoa(int(time.Now().Unix()))

	for _, dev := range devs {
		message.RegistrationIDs = append(message.RegistrationIDs, dev.GcmId)
	}

	jsonObj, _ := json.Marshal(message)

	c.Debugf("send_notifications: gcm request %v", string(jsonObj))

	client := urlfetch.Client(c)
	req, _ := http.NewRequest("POST", "https://android.googleapis.com/gcm/send", bytes.NewReader(jsonObj))
	req.Header.Set("Authorization", "key=AIzaSyB9evM2EZNT7yA4s8nxGFObMd7kOlBFkgk")
	req.Header.Set("Content-Type", "application/json")

	res, err := client.Do(req)

	if err != nil {
		c.Errorf("send_notifications: send message error: %v", err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	defer res.Body.Close()
	resBytes, _ := ioutil.ReadAll(res.Body)

	c.Debugf("send_notifications: gcm response %v", string(resBytes))

	n := Notification{
		TimeSent : time.Now(),
	}

	_, err = datastore.Put(c, messageid, &n)

	if err != nil {
		c.Errorf("send_notifications: couldn't put message to DB: %v", err.Error())
	}
}

type Delivery struct {
	UserId        string  `json:"user_id"`
	MessageId     string  `json:"message_id"`
	ConnectionType int    `json:"connection_type"`
	ServerTime     int    `json:"server_time"`
	TimeReceived  time.Time
	TimeSpent      int
}

func list_notifications(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	var ns []Notification
	keys, err := datastore.NewQuery("notification").GetAll(c, &ns)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	for i, n := range ns {
        k := keys[i]
		fmt.Fprint(w, k, " ", n, "\n")
	}

	var ds []Delivery
	keys, err = datastore.NewQuery("delivery").GetAll(c, &ds)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	for i, d := range ds {
        k := keys[i]
		fmt.Fprint(w, k, " ", d, "\n")
	}
}

func delivery_notify(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	c.Debugf("delivery: start")

	defer r.Body.Close()

	jsonBytes, _ := ioutil.ReadAll(r.Body)
	c.Debugf("Request: %s", string(jsonBytes))

	var d Delivery
	json.NewDecoder(bytes.NewReader(jsonBytes)).Decode(&d)

	d.TimeReceived = time.Now()
	d.TimeSpent    = int(time.Now().Unix()) - d.ServerTime

	id     := datastore.NewIncompleteKey(c, "delivery", nil)
	_, err := datastore.Put(c, id, &d)

	if err != nil {
		c.Errorf("delivery: datastore.Put error %s", err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}

	fmt.Fprint(w, strconv.Itoa(d.TimeSpent))
}

func export_delivery(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	c.Debugf("export_delivery: start")

	var ds []Delivery
	_, err := datastore.NewQuery("delivery").GetAll(c, &ds)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	for _, d := range ds {
		fmt.Fprint(w, d.UserId, " ", d.ConnectionType, " ", int(d.TimeReceived.Unix()), " ", d.TimeSpent, "\n")
	}
}

type OnlineNotify struct {
	UserId       string
	TimeReceived time.Time
}

func notify_online(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	
	c.Debugf("notify_online: start")

	user := r.FormValue("user")

	c.Debugf("notify_online: user=%s", user)

	var record OnlineNotify
	record.TimeReceived = time.Now()
	record.UserId       = user
	
	id     := datastore.NewIncompleteKey(c, "online_notify", nil)
	_, err := datastore.Put(c, id, &record)

	if err != nil {
		c.Errorf("online_notify: datastore.Put error %s", err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}
